/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2012/07/11
* File: CameraSceneNode.cpp
* Blog: http://www.cnblogs.com/zengqh/
**/

#include "SceneManager.h"
#include "../RenderSystem/RenderSystem.h"
#include "CameraSceneNode.h"

CameraSceneNode::CameraSceneNode(SceneNode* parent, 
								 SceneManager* scene_mgr, 
								 s32 id /* = -1 */, 
								 s32 type /* = 0 */, 
								 const glm::vec3& position)
								 : SceneNode(parent, 
								 scene_mgr, 
								 id, type, 
								 position)
								 , _camera_vertical_angle(88.0f)
{

}

CameraSceneNode::~CameraSceneNode()
{

}

const glm::mat4& CameraSceneNode::getProjMatrix()
{
	if (_proj_matrix_dirty)
	{
		_proj_matrix_dirty = false;

		_proj_matrix = glm::perspective(_fov, _aspect, _near_plane, _far_plane);
	}

	return _proj_matrix;
}

const glm::mat4& CameraSceneNode::getViewMatrix()
{
	if (_view_matrix_dirty)
	{
		_view_matrix_dirty = false;
		_view_matrix = glm::lookAt(_eye_pos, _target_pos, _up_vector);
	}

	return _view_matrix;
}

const glm::mat4& CameraSceneNode::getViewProjMatrix()
{
	if (_view_proj_matrix_dirty)
	{
		_view_proj_matrix_dirty = false;
		_view_proj_matrix = getProjMatrix() * getViewMatrix();
	}

	return _view_proj_matrix;
}

const glm::vec3& CameraSceneNode::getEyePos() const
{
	return _eye_pos;
}

void CameraSceneNode::setEyePos(const glm::vec3& eye_pos)
{
	_eye_pos = eye_pos;

	setViewDirty();
}

const glm::vec3& CameraSceneNode::getTargetPos() const
{
	return _target_pos;
}

void CameraSceneNode::setTargetPos(const glm::vec3& target_pos)
{
	_target_pos = target_pos;

	setViewDirty();
}

const glm::vec3& CameraSceneNode::getUpVector() const
{
	return _up_vector;
}

void CameraSceneNode::setUpVector(const glm::vec3& up_vector)
{
	_up_vector = up_vector;

	setViewDirty();
}

void CameraSceneNode::lookAt(
			const glm::vec3& eye_pos, 
			const glm::vec3& target_pos,
			const glm::vec3& up_vector)
{
	_eye_pos = eye_pos;
	_target_pos = target_pos;
	_up_vector = up_vector;

	setViewDirty();
}

void CameraSceneNode::setPerspectiveFov(f32 fov, f32 aspect, f32 near_plane, f32 far_plane)
{
	_fov = fov;
	_aspect = aspect;
	_near_plane = near_plane;
	_far_plane = far_plane;

	setProjDirty();
}

f32 CameraSceneNode::getNearPlane() const
{
	return _near_plane;
}

f32 CameraSceneNode::getFarPlane() const
{
	return _far_plane;
}


void CameraSceneNode::yaw(f32 angle)
{
	if (iszero(angle)) return;

	glm::mat4 mat;
	mat = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f));

	glm::vec3 look = _target_pos - _eye_pos;

	glm::vec4 look_rotate4 = mat * glm::vec4(look.x, look.y, look.z, 1.0f);

	glm::vec3 look_rotate = glm::vec3(look_rotate4.x, look_rotate4.y, look_rotate4.z);

	_target_pos = _eye_pos + look_rotate;

	setViewDirty();
}

void CameraSceneNode::yawTarget(f32 angle)
{
	if (iszero(angle)) return;

	glm::mat4 mat;
	mat = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f));

	glm::vec3 look = -_target_pos + _eye_pos;

	glm::vec4 look_rotate4 = mat * glm::vec4(look.x, look.y, look.z, 1.0f);

	glm::vec3 look_rotate = glm::vec3(look_rotate4.x, look_rotate4.y, look_rotate4.z);

	_eye_pos = _target_pos + look_rotate;

	setViewDirty();
}

void CameraSceneNode::pitch(f32 angle)
{
	if (iszero(angle)) return;

	glm::vec3 look = _target_pos - _eye_pos;

	glm::vec3 relativeRotation = getHorizontalAngle(look);

	relativeRotation.x += angle;

	if (relativeRotation.x > _camera_vertical_angle*2 &&
		relativeRotation.x < 360.0f-_camera_vertical_angle)
	{
		return;
	}
	else if (relativeRotation.x > _camera_vertical_angle &&
			relativeRotation.x < 360.0f-_camera_vertical_angle)
	{
		return;
	}

	glm::mat4 mat;
	
	mat = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(look.z, 0.0f, -look.x));

	glm::vec4 look_rotate4 = mat * glm::vec4(look.x, look.y, look.z, 0.0f);

	glm::vec3 look_rotate = glm::vec3(look_rotate4.x, look_rotate4.y, look_rotate4.z);

	_target_pos = _eye_pos + look_rotate;

	setViewDirty();
}

void CameraSceneNode::roll(f32 angle)
{
	/* not support now */
	/* up vector is alway fixed */
	
}

void CameraSceneNode::move(f32 distance)
{
	if (iszero(distance)) return;
	glm::vec3 look = _target_pos - _eye_pos;

	/* for fps */
	look.y = 0;

	glm::vec3 v_dis = look * distance;

	_target_pos += v_dis;
	_eye_pos += v_dis;

	setViewDirty();
}

void CameraSceneNode::moveLR(f32 distance)
{
	if (iszero(distance)) return;
	glm::vec3 look = _target_pos - _eye_pos;

	/* for fps */
	look.y = 0;

	glm::vec3 v_dis = look * distance;

	v_dis = glm::vec3(v_dis.z, 0, -v_dis.x);

	_target_pos += v_dis;
	_eye_pos += v_dis;

	setViewDirty();
}

void CameraSceneNode::render()
{
	RenderSystem* render_system = _scene_mgr->getRenderSystem();

	render_system->setTransform(TS_View, getViewMatrix());

	render_system->setTransform(TS_Projection, getProjMatrix());
}

void CameraSceneNode::setViewDirty()
{
	_view_matrix_dirty = true;
	_view_proj_matrix_dirty = true;
}

void CameraSceneNode::setProjDirty()
{
	_proj_matrix_dirty = true;
	_view_proj_matrix_dirty = true;
}


